#ifndef CARTO_TIME_H
#define CARTO_TIME_H

#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <string>
#include <map>
#include <deque>
#include <memory>

#include "glog/logging.h"

typedef std::chrono::steady_clock::time_point chrono_time;

inline chrono_time chrono_now()
{
    return std::chrono::steady_clock::now();
}

inline double ChronoMs(const chrono_time &start, const chrono_time &end)
{
    return std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(start - end).count();
}

inline int ChronoNowMs()
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}

inline int ChronoStampMs(const chrono_time &time)
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(time.time_since_epoch()).count();
}

namespace cartographer
{
    class PerfAppraise
    {
    public:
        using Ptr = std::shared_ptr<PerfAppraise>;

        PerfAppraise(size_t queueSize = 10)
            : queueSize_(queueSize)
        {
            timeLast_ = chrono_now();
        }

        inline int Reset()
        {
            timeLast_ = chrono_now();
            return ChronoStampMs(timeLast_);
        }

        inline double Update()
        {
            auto timeNow = chrono_now();
            double elapse = ChronoMs(timeNow, timeLast_);
            timeLast_ = timeNow;
            costQueue_.push_back(elapse);
            while (costQueue_.size() > queueSize_)
                costQueue_.pop_front();
            return elapse;
        }

        inline double Average()
        {
            auto size = costQueue_.size();
            if (size < 2)
                return 0.0;
            double total = 0.0;
            for (auto &cost : costQueue_)
                total += cost;
            return total / (double)size;
        }

    private:
        size_t queueSize_;
        chrono_time timeLast_;
        std::deque<double> costQueue_;
    };

    class PerfSet
    {
    public:
        static PerfSet &GetInstance()
        {
            static PerfSet inst;
            return inst;
        }

        static inline int Reset(const char *name)
        {
            std::string key(name);
            if (!Find(key))
                return 0.0;
            return Get(key)->Reset();
        }

        static inline double Update(const char *name, bool print = false)
        {
            std::string key(name);
            if (!Find(key))
                return 0.0;
            double cost = Get(key)->Update();
            if (print)
                LOG(INFO) << name << " cost " << cost;
            return cost;
        }

    private:
        static inline PerfAppraise::Ptr &Get(const std::string &key)
        {
            auto &inst = PerfSet::GetInstance();
            return inst.perfs_[key];
        }

        static inline bool Find(const std::string &key)
        {
            auto &inst = PerfSet::GetInstance();
            if (inst.perfs_.find(key) == inst.perfs_.end())
            {
                inst.perfs_[key] = std::make_shared<PerfAppraise>();
                return false;
            }
            return true;
        }

        void operator delete(void *ptr) = delete;
        void operator delete(void *ptr, const std::nothrow_t &) noexcept = delete;
        void *operator new(std::size_t size) = delete;
        void *operator new(std::size_t size, const std::nothrow_t &) noexcept = delete;

        std::map<std::string, PerfAppraise::Ptr> perfs_;
    };
} // namespace cartographer

#endif